#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include <string.h>
#include <sched.h>
#include <ctype.h>
#include <limits.h>
#include <fcntl.h>
#include <ncurses.h>
#include <panel.h>
#include <signal.h>

#define MAX_IRQ_CNT             256
#define BUFFER_SIZE             4096
#define currTime()              ({struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec+(10e-6)*ts.tv_nsec;})
#define max(a,b)                (((a)>(b))?(a):(b))
#define min(a,b)                (((a)<(b))?(a):(b))
#define swap(a,b)               ({typeof(a) _x; _x=(a); (a)=(b); (b)=_x;})


#define irqSampleCount(iif)      (((iif)->cpu_count)*((iif)->irq_number))
/**
 * cpu_index and irq_index are not indeies of all cpus or irqs, 
 * but the index of cpus and irqs monitored.
 */
#define cpuIrqCount(irq, cpu_index, irq_index, cpu_count)    \
    (irq)[(irq_index) *(cpu_count) + (cpu_index)]

static int has_smi = 0;

struct irqInfo_tag {
    int cpu_amount;         // number of cpus in total
    int *cpu_list;          // list of cpus to monitor
    int cpu_count;          // number of cpus to monitor
    char irq_name[256][4];
    char irq_longname[256][32];
    int irq_sort_index[256];
    int irq_number;         // number of irq
    int irq_cnt_show;
    double sample_start;    // time to start
    double sample_last;     // time of last sample
    int intervals[3];       // 1s, 60s, 300s by default.
    int *irq_last;          // irq records of the last sample.
    int *irq_curr;
    int *irq_delta[3];
    int *irq_bytime[3600];  // record the last irq delta 
    unsigned long long sample_count;    // how many seconds passed.
    int smi_curr;           // smi irq count
    int smi_delta[3];
    int smi_bytime[3600];
    int show_type;
};
typedef struct irqInfo_tag irqInfo_t;


void cpuid(int info[4], int level) {
    __asm__ __volatile__ (
        "cpuid"
        : "=a" (info[0]), "=b" (info[1]), "=c" (info[2]), "=d" (info[3])
        : "a" (level)
    );
}

void cpu_has_smi()
{
    if ((getuid() != 0) || (geteuid() != 0)) {
        fprintf(stderr, "Need to run as root to enable monitoring smi.\n");
        has_smi = 0;
        return;
    }
    int info[4];
    cpuid(info, 0);
    
    if (info[3] & (1<<5)) 
        has_smi = 1;
    else
        has_smi = 0;
}


void getCpuInterrupts(int *cpu_list, int *cpu_interrupt) {
    FILE *fp;
    char line[256];
    char *token;
    int cpu_id;

    fp = fopen("/proc/interrupts", "r");
    if (fp == NULL) {
        perror("Fail to open /proc/interrupts !");
        return;
    }

    (void)fgets(line, sizeof(line), fp);
    while (fgets(line, sizeof(line), fp)) {
        token = strtok(line, " ");
        cpu_id = 0;

        while (token != NULL) {
            if (cpu_id > 0) {
                for (int i = 0; cpu_list[i] != -1; i++) {
                    if (cpu_list[i] == cpu_id - 1) {
                        cpu_interrupt[cpu_list[i]] = atoi(token);
                        break;
                    }
                }
            }
            token = strtok(NULL, " ");
            cpu_id++;
        }
    }

    fclose(fp);
}

/**
 *        CPU# CPU## CPU###
 * IRQ#     **   **     ***
 * IRQ##    **   **     ***
 * ......
 */
void print_irq(int *irq, int irq_count, int cpu_count)
{
    char buf[16];

    printw("      ");
    for (int ii=0; ii<cpu_count; ++ii) {
        snprintf(buf, 16, "CPU%d", ii);
        printw("%9s", buf);
    }
    printw("\n");
    for (int ii=0; ii<irq_count; ++ii) {
        snprintf(buf, 16, "IRQ%d", ii);
        printw("%6s", buf);
        for (int jj=0; jj<cpu_count; ++jj) {
            printw("%9d", cpuIrqCount(irq, jj, ii, cpu_count));
        }
        printw("\n");
    }
}

void print_delta(irqInfo_t *irqInfo, int idx)
{
    int start=0, end=3;

    if (idx<3 && idx>=0) {
        start = idx;
        end = idx+1;
    }

    for(int ii=start; ii<end; ++ii) {
        print_irq(irqInfo->irq_delta[ii], irqInfo->irq_number, irqInfo->cpu_count);
    }
}


int get_smi()
{
    char buf[32];
    unsigned long long val=0;
    int fd;
    int ret;

    if (has_smi == 0)
        return -1;
    snprintf(buf, sizeof(buf), "/dev/cpu/%d/msr", 0);
    if ((fd = open(buf, O_RDONLY)) < 0) {
        if (system("modprobe msr") < 0)
            return -1; 
        if ((fd = open(buf, O_RDONLY)) < 0)
            return -1; 
    }   
#define MSR_SMI_COUNT  (0x00000034)
    ret = pread(fd, &val, 8, MSR_SMI_COUNT);
    (void)close(fd);
    if (ret < 0)
        return -1;
    else
        return (int)val;
}


void getIrqs(int *irq, int *cpu_list, int cpu_count)
{
    FILE *fp;
    char buffer[BUFFER_SIZE];
    char *token;
    int cpu_col_index;
    int irq_row_idx;
    int cpu_list_index = 0;

    fp = fopen("/proc/interrupts", "r");
    if (fp == NULL) {
        perror("Failed to open /proc/interrupts");
        return;
    }

    cpu_col_index = 0;
    irq_row_idx = 0;
    (void)fgets(buffer, BUFFER_SIZE, fp);     // omit the first row.
    while (fgets(buffer, BUFFER_SIZE, fp) != NULL) {
        cpu_list_index = 0;
        cpu_col_index = 0;
        token = strtok(buffer, " ");
        while (token != NULL) {
            token = strtok(NULL, " ");
            if (token != NULL) {
                if (cpu_list[cpu_list_index] == cpu_col_index) {
                    //printf("%6s", token);
                    cpuIrqCount(irq, cpu_list_index, irq_row_idx, cpu_count) = atoi(token);
                    cpu_list_index++;
                }
            }
            ++cpu_col_index;
        }
        //printf("\n");
        ++irq_row_idx;
    }

    //print_irq(irq, irq_row_idx, cpu_count);
    fclose(fp);
    
}


void update_irq_delta(irqInfo_t *irqInfo)
{
    if (irqInfo->sample_count == 0)
        return;

    // 1s by default.
    for (int ii=0; ii<3; ++ii) {
        if (irqInfo->sample_count >= irqInfo->intervals[ii]) {
            int offset  = irqInfo->intervals[ii];
            int index   = (irqInfo->sample_count-offset)%3600;
            for (int jj=0; jj<irqSampleCount(irqInfo); ++jj) {
                irqInfo->irq_delta[ii][jj] = (irqInfo->irq_curr[jj] - irqInfo->irq_bytime[index][jj]);
            }
            if (has_smi) 
                irqInfo->smi_delta[ii] = irqInfo->smi_curr - irqInfo->smi_bytime[index];
        } else {
            // not enough samples
            for (int jj=0; jj<irqSampleCount(irqInfo); ++jj) {
                int _temp = 0;
                _temp = (irqInfo->irq_curr[jj] - irqInfo->irq_bytime[0][jj]);
                irqInfo->irq_delta[ii][jj] = _temp;
            }
            if (has_smi) 
                irqInfo->smi_delta[ii] = irqInfo->smi_curr - irqInfo->smi_bytime[0];
        }
    }
}


void update_irq_1s(irqInfo_t *irqInfo)
{
    // sample data store in irq_curr
    getIrqs(irqInfo->irq_curr, irqInfo->cpu_list, irqInfo->cpu_count);
    irqInfo->smi_curr = get_smi();

    // update irq_delta with irq_curr
    update_irq_delta(irqInfo);

    //print_delta(irqInfo, 0);
    int _index = irqInfo->sample_count%3600;
    memcpy(irqInfo->irq_bytime[_index], irqInfo->irq_curr, sizeof(int)*irqSampleCount(irqInfo));
    irqInfo->smi_bytime[_index] = irqInfo->smi_curr;

    //print_irq(irqInfo->irq_bytime[irqInfo->irq_index], irqInfo->irq_number, irqInfo->cpu_count);
    // update lastest index.
    ++irqInfo->sample_count;
}

void sort_irq_by_count(irqInfo_t *irqInfo, int *_irq_count)
{
    //int irq_amount_by_type[256];
    for (int i = 0; i < 256; i++) {
        //irq_amount_by_type[i] = 0;
        irqInfo->irq_sort_index[i] = i;
    }
    for (int i = 0; i < irqInfo->irq_number; i++) {
        for (int j = i + 1; j < irqInfo->irq_number; j++) {
            if (_irq_count[irqInfo->irq_sort_index[i]] < _irq_count[irqInfo->irq_sort_index[j]])
                swap(irqInfo->irq_sort_index[i], irqInfo->irq_sort_index[j]);
        }
    }
}


// initialize irqInfo instance.
void initIrqInfo(irqInfo_t *irqInfo, char *cpu_list_raw)
{
    irqInfo->cpu_amount = (int) sysconf(_SC_NPROCESSORS_ONLN);
    // 1 init irqInfo->list with cpu_list_raw
    char *token;
    char *saveptr;
    int index = 0;

    // init cpu_count & cpu_list.
    printf("cpu amount : %d\n", irqInfo->cpu_amount);
    if (cpu_list_raw == NULL) {
        irqInfo->cpu_list = (int *)malloc(sizeof(int) * irqInfo->cpu_amount);
        for (int i = 0; i < irqInfo->cpu_amount; i++)
            irqInfo->cpu_list[i] = i;
        irqInfo->cpu_count = irqInfo->cpu_amount;
    } else {
        printf("cpu list(raw) is `%s`\n", cpu_list_raw);
        size_t supportCpuCnt = 64;
        irqInfo->cpu_list = (int *)malloc(sizeof(int) * supportCpuCnt);
        token = strtok_r(cpu_list_raw, ",", &saveptr);
        printf("token : %s\n", token);
        while (token != NULL) {
            char *dash = strchr(token, '-');
            if (dash != NULL) {
                int start = strtol(token, NULL, 10);
                int end = strtol(dash + 1, NULL, 10);
                printf("start %d, end %d\n", start, end);
                for (int i = start; i <= end; i++) {
                    irqInfo->cpu_list[index] = i;
                    ++index;
                    if (index >= supportCpuCnt) {
                        supportCpuCnt *= 2;
                        irqInfo->cpu_list = (int *)realloc(irqInfo->cpu_list, sizeof(int) * supportCpuCnt);
                    }
                }
            } else {
                irqInfo->cpu_list[index] = strtol(token, NULL, 10);
                ++index;
                if (index >= supportCpuCnt) {
                    supportCpuCnt *= 2;
                    irqInfo->cpu_list = (int *)realloc(irqInfo->cpu_list, sizeof(int) * supportCpuCnt);
                }
            }
            token = strtok_r(NULL, ",", &saveptr);
        }
        irqInfo->cpu_count = index;
    }
    
    printf("cpus(%d) : ", irqInfo->cpu_count);
    for (int ii=0; ii<irqInfo->cpu_count; ++ii)
        printf("%d ", irqInfo->cpu_list[ii]);
    printf("\n");

    // 2 init time intervals by default values.
    irqInfo->intervals[0] = 10;
    irqInfo->intervals[1] = 600;
    irqInfo->intervals[2] = 3600;

    // 3 update irq_name && irq_amount
    FILE *file;
    index=0;
    file = fopen("/proc/interrupts", "r");
    if (file == NULL) {
        perror("Failed to open /proc/interrupts");
        return;
    }

    // how many interrupts for each type of interrupts
    int _irq_count[256];
    char *last_token = NULL;
    char buffer[BUFFER_SIZE];
    int irq_idx = 0;
    (void)fgets(buffer, BUFFER_SIZE, file);
    while (fgets(buffer, BUFFER_SIZE, file) != NULL) {
        int _irq_cnt= 0;
        int first_token = 1;
        int cpu_idx = 0;
        token = strtok(buffer, " :");
        while (1) {
            if (token != NULL) {
                if (first_token == 1) {
                    int len_ss = (int)strlen(token);
                    memcpy(irqInfo->irq_name[irq_idx], token, min(3, len_ss));
                    irqInfo->irq_name[irq_idx][3] = '\0';
                    first_token = 0;
                } else {
                    if (cpu_idx < irqInfo->cpu_amount) {
                        _irq_cnt += atoi(token);
                        ++cpu_idx;
                    }
                }
            } else {
                int len_ss = (int)strlen(last_token);
                do {
                    if (last_token[len_ss-1] != '\n' && last_token[len_ss-1] != '\r')
                        break;
                } while(--len_ss);
                memcpy(irqInfo->irq_longname[irq_idx], last_token, min(31, len_ss));
                irqInfo->irq_longname[irq_idx][31] = '\0';
                break;
            }
            last_token = token;
            token = strtok(NULL, " ");
        }
        _irq_count[irq_idx] = _irq_cnt;
        ++irq_idx;
    }
    fclose(file);
    irqInfo->irq_number = irq_idx;

    printf("irqs(%d) are : ", irqInfo->irq_number);
    for (int ii=0; ii<irqInfo->irq_number; ++ii) {
        printf("`%s`(%d,`%s`)\n", irqInfo->irq_name[ii], _irq_count[ii], irqInfo->irq_longname[ii]);
    }
    printf("\n");

    // sort irqInfo->irq_sort_index by _irq_count in descending order
    sort_irq_by_count(irqInfo, _irq_count);
    // for debug, output most active irq
    for (int ii=0; ii<irqInfo->irq_number; ++ii) {
        int idx = irqInfo->irq_sort_index[ii];
        printf("[%s] %d [%s]\n", irqInfo->irq_name[idx], _irq_count[idx], irqInfo->irq_longname[idx]);
    }

    //
    for (int i = 0; i < 3600; i++) {
        irqInfo->irq_bytime[i] = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
        memset(irqInfo->irq_bytime[i], 0, sizeof(int) * irqSampleCount(irqInfo));
    }
    irqInfo->irq_last = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
    memset(irqInfo->irq_last, 0, sizeof(int) * irqSampleCount(irqInfo));
    irqInfo->irq_curr = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
    memset(irqInfo->irq_curr, 0, sizeof(int) * irqSampleCount(irqInfo));
    irqInfo->irq_delta[0] = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
    memset(irqInfo->irq_delta[0], 0, irqSampleCount(irqInfo));
    irqInfo->irq_delta[1] = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
    memset(irqInfo->irq_delta[1], 0, irqSampleCount(irqInfo));
    irqInfo->irq_delta[2] = (int *)malloc(sizeof(int) * irqSampleCount(irqInfo));
    memset(irqInfo->irq_delta[2], 0, irqSampleCount(irqInfo));

    irqInfo->sample_count = 0;
    // get irqs from /proc/interrupts
    update_irq_1s(irqInfo);

    // x get current seconds
    irqInfo->sample_start = currTime();
}


void freeIrqInfo(irqInfo_t *irqInfo)
{
    if (irqInfo) {
        free(irqInfo->cpu_list);
        free(irqInfo->irq_last);
        free(irqInfo->irq_curr);
        free(irqInfo);
    }
}



void init_ui()
{
    initscr();
    start_color();
#define RGB_TO_CURSES(x) ((int)((float)x*3.90625))  // 1000/256 conversion
    short badger_red = COLOR_WHITE + 1;
    init_color(badger_red, RGB_TO_CURSES(197), RGB_TO_CURSES(5), RGB_TO_CURSES(12));
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_BLUE, COLOR_BLACK);
    init_pair(3, COLOR_WHITE, COLOR_BLACK);
    bkgd(COLOR_PAIR(0));
    cbreak();
    noecho();
    refresh();
}



/**
 *  at most 255 interrupts are shown.
 *         delta0|1|2
 *                    LOC           IWI
 *  CPU0   xxxx|xxxx|xxxx    xxxx|xx|xx
 *  CPU1     xx|  xx|  xx      xx|xx|xx
 *   ...
 * CPU10      x|   x|   x       x| x| x
 * 
 */
static int * fmt_width = NULL;
#define ui_width(iif, irq, intv)    fmt_width[3*(irq)+(intv)]

void update_fmt_width(irqInfo_t *irqInfo)
{
    int max_width = 0;
    for (int ii=0; ii <3; ++ii) {
        // set ui_width(iif, cpu, irq, intv))
        for (int jj=0; jj<irqInfo->irq_number; ++jj) {
            max_width = 0;
            for (int pp=0; pp<irqInfo->cpu_count; ++pp) {
                int _num = max(1, cpuIrqCount(irqInfo->irq_delta[ii], pp, jj, irqInfo->cpu_count));
                int digits = (int)log10(_num) + 1;
                max_width = max(digits, max_width);
                //printw("-%d(%d)-", max_width, digits);
            }
            ui_width(irqInfo, jj, ii) = max_width;
            //printw("(irq %d, intv %d) %d\n", jj, ii, ui_width(irqInfo, jj, ii));
        }
    }
}

static inline int is_all_digits(const char *str) {
    if (*str == '\0') return 0;

    for (int i = 0; str[i] != '\0'; i++) {
        if (!isdigit(str[i])) {
            return 0;
        }
    }
    return 1;
}

void update_ui(irqInfo_t *irqInfo)
{
    char buf[32];
    int seg_len[16];
    int cpu_count = irqInfo->cpu_count;
    int irq_cnt_shown = 0;

    memset(buf, 0, 32);
    if (fmt_width == NULL) {
        fmt_width = (int *)malloc(3 * 255 * sizeof(int));
    }
    update_fmt_width(irqInfo);

    // sort by delta[2]
    int _irq_count[256];
    for (int ii=0; ii<irqInfo->irq_number; ++ii) {
        _irq_count[ii] = 0;
        for (int jj=0; jj<irqInfo->cpu_count; ++jj) {
            int _irq = cpuIrqCount(irqInfo->irq_delta[2], jj, ii, irqInfo->cpu_count);
            _irq_count[ii] += _irq;
        }
    }
    sort_irq_by_count(irqInfo, _irq_count);
    for (int ii=0; ii<irqInfo->irq_number; ++ii) {
        int index = irqInfo->irq_sort_index[ii];
        if ( _irq_count[index] == 0) {
            irq_cnt_shown = ii+1;
            break;
        }
    }

    // count at most how many interrupts can be shown.
    int y, x;
    getmaxyx(stdscr, y, x);
    int _wid_cnt = (x-7)/10;   // 10 is length of `**|**|**  `
    irq_cnt_shown = min(irq_cnt_shown, _wid_cnt);

    // print top most interrupts
    wclear(stdscr);
    printw("       ");
    for (int ii=0; ii<irq_cnt_shown; ++ii) {
        seg_len[ii] = ui_width(irqInfo, irqInfo->irq_sort_index[ii], 0) +
            ui_width(irqInfo, irqInfo->irq_sort_index[ii], 1) +
            ui_width(irqInfo, irqInfo->irq_sort_index[ii], 2) +
            2;
        //printw("seg_len : %d\n", seg_len);
        snprintf(buf, 32, "%*s  ", seg_len[ii], irqInfo->irq_name[irqInfo->irq_sort_index[ii]]);
        //printw("%*s", seg_len[ii],  buf);
    }
    for (int ii=0; ii<irq_cnt_shown; ++ii) {
        char buf[9];
        int _irq_index = irqInfo->irq_sort_index[ii];
        if (is_all_digits(irqInfo->irq_name[_irq_index])) {
            strncpy(buf, irqInfo->irq_longname[_irq_index], 8);
        } else {
            strncpy(buf, irqInfo->irq_name[_irq_index], 3);
            buf[3] = '\0';
        }
        printw("%8s  ", buf);
    }
    printw("\n");
    for (int pp=0; pp<irqInfo->cpu_count; ++pp) {   // for all list cpus
        snprintf(buf, 32, "CPU%d", irqInfo->cpu_list[pp]);
        printw("%5s  ", buf);
        for (int jj=0; jj<irq_cnt_shown; ++jj) {    // for all increased irqs,  `**|**|**  `
            for (int kk=0; kk<3; ++kk) {            // for all deltas
                int *_irq = irqInfo->irq_delta[kk];
                int _count = cpuIrqCount(_irq, pp, irqInfo->irq_sort_index[jj], cpu_count);
                if (_count > 99)
                    printw("**");
                else {
                    if (_count > 0 ) {
                        attron(COLOR_PAIR(kk+1));
                        printw("%2d", _count);
                        attroff(COLOR_PAIR(kk+1));
                    } else printw("%2d", _count);
                }
                if (kk != 2)    printw("|");
            }
            printw("  ");
        }
        printw("\n");
    }
    if (has_smi) {
        printw("   SMI %2d|%2d|%2d (%d in total)\n", 
                irqInfo->smi_delta[0],
                irqInfo->smi_delta[1],
                irqInfo->smi_delta[2], irqInfo->smi_curr);
    }

    //print_delta(irqInfo, 1);
    //print_irq(irqInfo->irq_bytime[irqInfo->sample_count-1], irqInfo->irq_number, irqInfo->cpu_count);
    wrefresh(stdscr);
    //refresh();
}


static void quit_handler(int sig)
{
    endwin();
    exit(0);
}


int main(int argc, char **argv) 
{
    char *cpu_list_raw = NULL;
    irqInfo_t irqInfo;
    int opt;

    (void) signal(SIGINT, quit_handler);

    while ((opt = getopt(argc, argv, "p:i:t")) != -1) {
        size_t opt_len = strlen(optarg);
        switch (opt) {
            case 'p':   // processor list
                cpu_list_raw = (char *)malloc(opt_len+1);
                cpu_list_raw[opt_len] = '\0';
                strncpy(cpu_list_raw, optarg, opt_len);
                break;
            case 'i':   // minimum sample interval
                break;
            case 't':   // show type
                if (opt_len == 1) {
                    irqInfo.show_type = optarg[0] - '0';
                } else {
                    fprintf(stderr, "Unknown output type %s\n", optarg);
                }
                break;
            default:
                break;
        }
    }

    cpu_has_smi();
    initIrqInfo(&irqInfo, cpu_list_raw);
    init_ui();

    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    while (1) {
        ts.tv_sec = ts.tv_sec + 1;
        clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
        update_irq_1s(&irqInfo);
        update_ui(&irqInfo);
    }

    freeIrqInfo(&irqInfo);

    return 0;
}

